第十三章:CRUD 系統實戰整合
1. CRUD 流程解析
這套系統展示了完整的前端 CRUD (Create, Read, Update, Delete) 核心架構。透過 jQuery 的 AJAX 搭配 Bootstrap UI 與 SweetAlert2,完成了一套不需重新整理網頁的非同步資料處理流程。
步驟一:C - Create (新增資料)
對應檔案: 20260310-CRUD-index.html
新增資料的流程包含「表單驗證」與「資料拋送」兩個階段:
- 防呆與重複驗證 (GET):
當使用者輸入完「品名」並移開游標 (blur事件) 時,程式會先發送一個GET請求到http://localhost:3000/product?pname=...。這是為了檢查資料庫中是否已經存在相同的產品名稱。如果陣列長度大於 0,代表名稱重複,會立刻將輸入框標示為紅色錯誤 (invalid) 並阻擋後續送出。 - 打包 JSON 資料:
當使用者點擊「確認」且所有旗標 (flag) 皆為true時,會將表單內的值 (username, pname, pnum, size) 與當下時間 (createdAt) 打包成一個 JavaScript 物件。 - 發送新增請求 (POST):
使用 AJAX 的POST方法發送到後端 API。- 必須設定
contentType: "application/json"。 - 使用
JSON.stringify(jsonData)將物件轉為純文字字串送出。 - 成功後,呼叫 SweetAlert2 顯示成功動畫,並利用
.val("").blur()清空表單以利下一筆輸入。
- 必須設定
步驟二:R - Read (讀取與渲染列表)
對應檔案: 20260310-CRUD-showTable.html (前半部)
進入產品列表頁面時,第一件事就是把資料庫的資料抓出來顯示:
- 發送讀取請求 (GET):
在$(function() {...})(網頁載入完成) 時,立刻發送 AJAXGET請求到http://localhost:3000/product取得所有產品的 JSON 陣列。 - 動態渲染表格 (Render Table):
呼叫自訂函數renderTable(data),使用forEach迴圈遍歷所有資料。利用 ES6 樣板字面值 (反引號) 拼接出<tr>與<td>的 HTML 結構,最後用$("#mybody").append(strHTML)將其塞入網頁表格中。
在渲染「更新」與「刪除」按鈕時,程式碼大量使用了 HTML5 的
data-* 屬性(例如
data-id="${item.id}"、data-pname="${item.pname}")。這非常重要,這等於是把資料「綁」在按鈕上,讓後續點擊時能精準知道要操作哪一筆資料。
步驟三:U - Update (更新資料)
對應檔案: 20260310-CRUD-showTable.html (更新功能)
更新流程分為「帶入舊資料」與「送出新資料」:
因為表格的按鈕是 AJAX 非同步「事後」才長出來的,如果直接用
$(".updateBtn").click(...) 會監聽不到。必須使用
$(document).on("click", ".updateBtn", function() {...}) 將監聽綁定在父層文件上。
- 資料回填至 Modal:
點擊「更新」按鈕時,會透過$(this).data("id")等語法,將剛剛埋在按鈕上的舊資料抓出來,填入到 Bootstrap Modal 彈出視窗的對應輸入框中,並將唯一的id存入全域變數uid中備用。 - 發送更新請求 (PATCH):
在 Modal 中修改完數量或尺寸並點擊確認後,會觸發 AJAX 請求。- HTTP 方法: 使用
PATCH(用於部分更新) 或PUT(用於完全覆蓋)。這裡使用PATCH。 - URL 路由: 網址必須加上特定的 ID,例如
.../product/${uid},精準告訴後端要修改哪一筆。 - 成功後,使用
location.href重新載入頁面以顯示最新資料。
- HTTP 方法: 使用
步驟四:D - Delete (刪除資料)
對應檔案: 20260310-CRUD-showTable.html (刪除功能)
刪除是最具破壞性的動作,因此必須加上防護機制:
- 確認視窗:
點擊刪除按鈕時(同樣使用事件委派$(document).on(...)監聽),會先彈出 SweetAlert2 詢問「確認刪除?」。 - 取得唯一識別碼:
如果使用者點擊「確認」,則透過let delete_id = $(this).data("id");抓出要刪除的資料 ID。 - 發送刪除請求 (DELETE):
發送 AJAXDELETE請求至http://localhost:3000/product/${delete_id}。DELETE請求通常不需要傳遞data本體,只要 URL 中的 ID 正確即可。- 伺服器刪除成功後,前端執行網頁重整,完成資料畫面的同步。
2.新增資料 (Create)
現在我們正式進入 CRUD 的實作階段。第一步是建立讓使用者輸入資料的介面 (UI)。我們不使用傳統會造成網頁重整的 <form>,而是利用 Bootstrap
的卡片 (Card) 與獨立的輸入框元件來刻劃版面,準備搭配 AJAX 使用。
2-1. 準備 index 檔案、刻入架構
這段 HTML 程式碼負責建構「新增產品」的表單外觀,以及預先埋設「驗證提示」的區塊:
<div class="card shadow-lg">
<div class="card-header text-bg-danger fw-900 h3 text-center">新增產品</div>
<div class="card-body">
<!-- 1. 訂購者欄位 -->
<div class="mb-3">
<label for="username" class="form-label">訂購者名稱</label>
<input type="text" class="form-control" placeholder="字數1~8" id="username" name="username">
<div class="valid-feedback">符合規定</div>
<div class="invalid-feedback">不符合規定</div>
</div>
<!-- 2. 品名欄位 (具備專屬動態回饋 ID) -->
<div class="mb-3">
<label for="" class="form-label">品名</label>
<input type="text" class="form-control" placeholder="字數1~8" id="pname">
<div class="valid-feedback" id="pname_valid-feedback">符合規定</div>
<div class="invalid-feedback" id="pname_invalid-feedback">不符合規定</div>
</div>
<!-- 3. 數量欄位 -->
<div class="mb-3">
<label for="" class="form-label">數量(數量1~10)</label>
<input type="number" min="1" max="10" value="1" class="form-control" id="pnum">
<div class="valid-feedback">符合規定</div>
<div class="invalid-feedback">不符合規定</div>
</div>
<!-- 4. 尺寸選單 -->
<div class="mb-3">
<label for="" class="form-label">尺寸</label>
<select name="" id="size" class="form-select form-select-lg">
<option value="大">大</option>
<option value="中">中</option>
<option value="小">小</option>
</select>
</div>
<!-- 5. 按鈕區 -->
<div class="text-center my-4">
<button class="btn btn-outline-secondary">取消</button>
<button class="btn btn-primary" id="ok_btn">確認</button>
</div>
</div>
</div>
核心設計思維解析:
- UI 視覺包裝 (Card Component): 使用
.card搭配.shadow-lg(大陰影) 將表單包裝成一個獨立的浮動面板,提升整體畫面的層次感與專業度。 - ID 命名綁定 (The Hooks): 每個
<input>、<select>以及最後的確認按鈕 (#ok_btn) 都給予了明確的id。這些 ID 是 jQuery 後續用來「抓取使用者輸入值」和「綁定點擊事件」的唯一鉤子 (Hooks)。 - 預留驗證回饋區塊 (Validation Feedback): 這是此架構最關鍵的防呆設計。在每個輸入框下方,都預先寫好了
.valid-feedback(綠字) 與.invalid-feedback(紅字) 的 div 區塊。- 預設狀態下,Bootstrap 會將這兩個區塊隱藏。
- 未來在 JS 中,當我們針對 Input 加上
is-valid或is-invalid類別時,對應的提示字才會彈現出來。
仔細看第二區塊,品名的提示多加了
id="pname_valid-feedback"。這是因為「品名」的規則比較複雜,未來我們需要透過 AJAX
去資料庫檢查是否重複。如果有重複,我們必須用 JS 透過這個 ID 去動態替換裡面的文字(例如將原本的「不符合規定」改成精準的「該產品名稱已存在!」)。
2-2. 監聽與即時驗證 (GET)
當使用者填寫完畢並將游標移開 (blur) 時,程式會立刻進行驗證。這不僅能提升使用者體驗,也能確保在資料送到後端存檔前,前端已經過初步過濾。
A. 基礎邏輯與欄位監聽
針對「訂購者名稱」與「數量」,我們執行純前端的條件判斷,並透過 Flags (旗標變數) 記錄該欄位是否過關:
$("#username").blur(function () {
// 判斷字數是否在 1~8 之間
if ($(this).val().length > 0 && $(this).val().length < 9) {
$(this).removeClass("is-invalid").addClass("is-valid");
flag_username = true;
} else {
$(this).removeClass("is-valid").addClass("is-invalid");
flag_username = false;
}
});
B. 進階驗證:AJAX 檢查資料重複性
這是此步驟最核心的技術。當使用者輸入完「品名」後,前端會主動發送一個 GET 請求,詢問後端資料庫:「請問這個名字有人用了嗎?」
- 第一層: 先檢查字數是否符合規範 (1~8 字)。
- 第二層 (AJAX): 若字數合格,則發送請求至
http://localhost:3000/product?pname=${pname}。 - 判斷回傳結果:
- 若
data.length > 0:代表資料庫裡已經有這筆品名了。此時必須顯示錯誤,並透過.text()動態修改紅字提示內容。 - 若
data.length == 0:代表這是一個全新的品名,可以使用。
- 若
$("#pname").blur(function () {
let pname = $(this).val();
if (pname.length > 0 && pname.length < 9) {
// 發送 GET 請求進行唯一性檢查
$.ajax({
type: "GET",
url: `http://localhost:3000/product?pname=${pname}`,
dataType: "json",
success: function (data) {
if (data.length > 0) {
// 品名重複,顯示錯誤訊息
$("#pname").removeClass("is-valid").addClass("is-invalid");
$("#pname_invalid-feedback").text("該產品名稱已存在,不能使用此名稱!");
flag_pname = false;
} else {
// 品名可用,顯示成功訊息
$("#pname").removeClass("is-invalid").addClass("is-valid");
$("#pname_valid-feedback").text("該產品名稱可以使用!");
flag_pname = true;
}
},
error: function () {
// 若 API 連線失敗,使用 SweetAlert2 彈出警告
Swal.fire({ title: "產品驗證失敗", icon: "error" });
}
});
} else {
$(this).removeClass("is-valid").addClass("is-invalid");
flag_pname = false;
}
});
核心觀念解析:
blur事件的優勢: 不同於keyup(每打一個字就檢查一次,會造成伺服器負擔),blur只在使用者完成輸入並離開欄位時觸發一次,是效能與互動之間的最佳平衡。- 動態文字回饋 (Dynamic Feedback): 透過
$("#pname_invalid-feedback").text(...),我們可以在不更動 HTML 結構的情況下,根據後端回傳的具體原因(如:格式錯誤、名稱重複、伺服器斷線)來精準提示使用者。 - 連線錯誤處理: 在 AJAX 中加入
error區塊是非常重要的習慣。當後端 JSON Server 或 API 伺服器未啟動時,能及時透過 SweetAlert2 給予開發者或使用者明確的錯誤提醒。
2-3. 打包資料與發送 POST 請求 (Create)
當使用者填寫完畢並點擊「確認」按鈕時,我們會觸發 click 事件。此時必須經過最終確認,將資料打包成 JSON 格式,並以 POST 方法送到後端
API 寫入資料庫。
A. 最終防呆與資料封裝
在發送請求前,必須確保所有的驗證旗標 (Flags) 皆為 true。接著,將各個 Input 欄位的值抓出來,並加入當下的時間戳記,組合成一個 JavaScript 物件。
$("#ok_btn").click(function () {
// 1. 最終防呆檢查:只有全數通過,才執行新增
if (flag_username && flag_pname && flag_pnum) {
// 2. 將畫面上的資料封裝成 JS 物件
let jsonData = {
username: $("#username").val(),
pname: $("#pname").val(),
pnum: $("#pnum").val(),
size: $("#size").val(),
createdAt: new Date().toLocaleString() // 自動取得當下時間
};
// ... 接續 AJAX 發送 ...
B. 發送 AJAX POST 請求
將打包好的資料傳送給後端。請特別注意 POST 請求的必要設定:
// 3. 傳遞至後端 API
$.ajax({
type: "POST", // HTTP 請求方法:POST 用於「新增」
url: "http://localhost:3000/product",
contentType: "application/json", // 宣告傳送格式為 JSON
data: JSON.stringify(jsonData), // 將 JS 物件轉為 JSON 字串
success: function (data) {
// 新增成功:呼叫 SweetAlert2 顯示成功動畫
Swal.fire({
title: "新增成功!",
icon: "success",
confirmButtonText: "確認"
}).then((result) => {
// 使用者點擊「確認」後,清空輸入欄位以利下一筆輸入
if (result.isConfirmed) {
$("#username").val("").blur();
$("#pname").val("").blur();
$("#pnum").val("1").blur();
$("#size").val("大");
}
});
},
error: function () {
// API 連線失敗或伺服器出錯
Swal.fire({ title: "新增失敗", icon: "error" });
}
});
} else {
// 若有任何 flag 為 false,則擋下並提示使用者
Swal.fire({ title: "欄位有錯誤,請修正!", icon: "error" });
}
});
核心觀念解析:
JSON.stringify()與contentType: 在執行 POST 或 PATCH 等寫入動作時,這兩者缺一不可。必須明確告訴伺服器「我傳的是 JSON (contentType)」,並且確實「把物件轉成純文字字串傳輸 (stringify)」。- 優化 UX 的小技巧 (清空表單): 在 SweetAlert2 的
.then()區塊中,當成功寫入資料並獲得使用者確認後,我們使用.val("").blur()將欄位清空。特別注意.blur()的用意: 因為原本的欄位帶有綠色勾勾 (is-valid),透過強制觸發 blur,可以讓前面寫好的驗證機制重新啟動,把綠框洗掉,恢復成乾淨的原始狀態。
不同於傳統的
alert() 會直接卡死網頁執行緒,SweetAlert2
是一個非同步的彈出視窗。如果你希望「等使用者按下確認按鈕後,才執行某件事情」(例如跳轉頁面或清空表單),必須將後續的程式碼寫在
.then((result) => { ... }) 區塊之內。
3. 讀取與渲染列表 (Read)
完成新增功能後,我們需要一個專屬的頁面來顯示資料庫中所有的產品。這個頁面包含一個資料表格,以及一個隱藏的「更新用彈出視窗 (Modal)」。
3-1. 準備 showTable 檔案、刻入架構
在 20260310-CRUD-showTable.html 中,我們不寫死表格的內容,而是留下 id 讓 AJAX 抓到資料後動態填入。同時,我們將
Bootstrap 的 Modal 先寫在網頁底部備用。
<!-- 1. 資料列表區塊 -->
<div class="container pt-5">
<div class="display-4 text-primary fw-900 mb-3 text-center">顯示產品列表</div>
<table class="table table-bordered shadow-sm">
<thead class="table-dark">
<tr>
<th>編號</th>
<th>訂購者</th>
<th>品名</th>
<th>數量</th>
<th>尺寸</th>
<th>建檔時間</th>
<th>#</th> <!-- 預留給操作按鈕 (更新/刪除) -->
</tr>
</thead>
<!-- 這是最關鍵的 Hook,未來 AJAX 取得的資料都會 append 到這裡 -->
<tbody id="mybody">
<!-- 資料將由 JS 動態生成 -->
</tbody>
</table>
</div>
<!-- 2. 更新專用的彈出視窗 (Modal) -->
<div class="modal fade" id="updateModal" tabindex="-1" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header text-bg-warning">
<h1 class="modal-title fs-5 fw-900">產品更新</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<!-- 訂購者與品名:設定為 disabled (禁止修改) -->
<div class="mb-3">
<label class="form-label">訂購者名稱</label>
<input type="text" class="form-control" id="username" disabled>
</div>
<div class="mb-3">
<label class="form-label">品名</label>
<input type="text" class="form-control" id="pname" disabled>
</div>
<!-- 數量與尺寸:開放修改 -->
<div class="mb-3">
<label class="form-label">數量(1~10)</label>
<input type="number" class="form-control" id="pnum">
</div>
<div class="mb-3">
<label class="form-label">尺寸</label>
<select id="size" class="form-select form-select-lg">
<option value="大">大</option>
<option value="中">中</option>
<option value="小">小</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">取消</button>
<button type="button" class="btn btn-primary" id="update_btn">確認更新</button>
</div>
</div>
</div>
</div>
核心設計思維解析:
- 動態資料的容器:
<tbody id="mybody">是整個頁面的靈魂。因為我們不知道資料庫裡有幾筆資料,所以網頁初始狀態必須是空的,由 JavaScript 抓到資料後再來「填空」。 - Modal 視窗的預先掛載: Bootstrap 的 Modal 是透過 CSS
控制顯示與隱藏(預設隱藏)。我們把它寫在網頁底部,未來當使用者點擊表格中的「更新」按鈕時,只要透過屬性或 JS 呼叫
#updateModal即可喚醒它。 - 部分欄位鎖定 (
disabled): 在商業邏輯中,通常不允許修改核心欄位(如訂購者是誰、買了什麼商品),只能修改細節(如數量、尺寸)。因此,我們在 Modal 中針對username與pname加上了disabled屬性,這在畫面上會呈現灰色且無法點擊的狀態,是很常見的 UI 防呆設計。
3-2. 取得資料與狀態初始化 (GET)
在 showTable 頁面載入完成後,我們必須主動向後端請求資料,並將回傳的結果顯示在網頁表格中。這需要結合 AJAX GET 請求與「全域變數」的儲存機制。
A. 變數宣告與環境初始化
在主程式執行前,我們先定義幾個全域變數來儲存狀態與資料:
let productDATA = []; // 關鍵!用來儲存從後端抓回來的所有產品 JSON 資料
let uid; // 預留變數,用於後續「更新」功能時暫存該筆資料的唯一的 ID
let flag_pnum = true; // 驗證旗標,用於更新視窗中的數量驗證
B. 發送 AJAX GET 請求
透過 jQuery 的 $.ajax 方法,我們向指定的 API 網址請求所有產品資料:
$(function () {
// 當網頁載入完成,立刻執行取得資料的動作
$.ajax({
type: "GET", // 使用 GET 方法讀取資料
url: "http://localhost:3000/product",
dataType: "json", // 指定回傳格式為 JSON
success: function (data) {
// 請求成功:
console.log(data); // 在主控台確認資料內容
productDATA = data; // 將抓回來的陣列存入全域變數,供全網頁程式使用
// 呼叫渲染函數,將資料跑迴圈畫到 HTML 表格上
renderTable(productDATA);
},
error: function () {
// 請求失敗:代表伺服器未啟動或網址打錯
Swal.fire({
title: "讀取失敗",
text: "API 連結出錯:http://localhost:3000/product",
icon: "error"
});
}
});
});
核心觀念解析:
- 全域變數
productDATA的妙用: 我們特意將 API 回傳的資料儲存在全域變數中,而非直接畫完就丟。這樣做的好處是,未來如果要執行「前端即時關鍵字搜尋」或「欄位升降排序」時,就不需要再次驚動伺服器,直接操作這個變數即可,網頁反應會快非常多。 - 資料驅動介面 (Data-Driven): 這是一種現代開發思維。我們不直接操作 DOM 來改文字,而是「先處理資料 (JS 變數)」,再由專門的函數(如
renderTable)根據這份資料來產出畫面。 - 錯誤處理的必要性: AJAX 是非同步的,若後端掛掉,前端網頁通常會沒反應。因此,必須加上
error區塊並搭配 SweetAlert2 提醒,開發者才能第一時間知道是連線出了問題。
資料庫 (db.json) ➔ API Server (json-server) ➔ 前端 AJAX GET ➔ productDATA 變數 ➔ renderTable() 函數 ➔ 網頁顯示。
3-3. 動態渲染表格與資料埋設 (Render Table)
當 AJAX 成功抓回 productDATA 後,我們需要將這些記憶體中的數據逐一「畫」到網頁表格上。這需要透過 JavaScript 的迴圈機制來處理。
A. 遍歷資料與拼接 HTML
利用 forEach 迴圈遍歷陣列,並使用 ES6 樣板字面值 (反引號 ` ) 輕鬆處理多行字串,將物件屬性嵌入到
<tr> 與 <td> 標籤中。
function renderTable(data) {
// 遍歷從後端取得的每一筆產品物件 (item)
data.forEach(function (item) {
// 利用反引號建構整行表格列
let strHTML = `<tr>
<td>${item.id}</td>
<td>${item.username}</td>
<td>${item.pname}</td>
<td>${item.pnum}</td>
<td>${item.size}</td>
<td>${item.createdAt}</td>
<td>
<!-- 更新按鈕:埋入大量 data-* 屬性,為後續 U 流程鋪路 -->
<button class="btn btn-outline-warning updateBtn"
data-bs-toggle="modal"
data-bs-target="#updateModal"
data-id="${item.id}"
data-username="${item.username}"
data-pname="${item.pname}"
data-pnum="${item.pnum}"
data-size="${item.size}">更新</button>
<!-- 刪除按鈕:只需埋入 id 供刪除辨識 -->
<button class="btn btn-danger deleteBtn" data-id="${item.id}">刪除</button>
</td>
</tr>`;
// 將拼接好的 HTML 字串插入到表格主體 (#mybody) 之中
$("#mybody").append(strHTML);
});
}
B. 核心技巧:data-* 屬性的深度運用
這段程式碼最精妙的地方在於按鈕上的 data-* 自訂屬性。這等於是在每個按鈕上「貼標籤」。
- 為什麼要埋資料? 由於表格是動態生成的,當使用者點擊「更新」按鈕時,程式必須立刻得知「這筆資料原本是什麼內容」。
- 資料流向: 我們將資料庫裡的 ID、姓名、品名等資訊,利用
data-id="${item.id}"等語法直接塞進按鈕的 HTML 屬性中。 - 取用方式: 在後續的「更新 (Update)」步驟,我們只需要使用 jQuery 的
$(this).data("id")或$(this).data("pname"),就能輕易把資料從按鈕中取回,填入 Modal 彈出視窗。
在您的程式碼中,按鈕標籤寫為
<butoon>,實務開發時請記得更正為標準的
<button>,以確保瀏覽器能正確渲染樣式並獲得最佳的相容性。
4. 更新資料 (Update)
更新功能是 CRUD 中邏輯最複雜的一環,因為它需要先「讀取舊資料」,再「寫入新資料」。這通常會配合一個隱藏的表單或 Modal (彈出視窗) 來進行。
4-1. 事件委派與資料回填至 Modal
當使用者點擊表格中的「更新」按鈕時,我們必須把這筆資料的詳細內容填入 Modal 中。但這裡有一個極大的陷阱:表格是 AJAX 非同步「事後」畫上去的,傳統的 click()
監聽會失效!
// 錯誤寫法:$(".updateBtn").click(function(){...}) 會抓不到動態生成的按鈕!
// 正確寫法:事件委派 (Event Delegation)
// 將監聽器綁定在早就存在的 document 上,當點擊發生時,再去過濾是否為 .updateBtn
$(document).on("click", ".updateBtn", function () {
// 1. 抓取埋在按鈕上的 data-* 屬性
console.log($(this).data("id"));
console.log($(this).data("pname"));
// 2. 將重要的唯一識別碼 (ID) 存入全域變數 uid 中,稍後更新 API 會用到
uid = $(this).data("id");
// 3. 將抓到的舊資料,回填 (val) 到 Modal 的輸入框內
$("#username").val($(this).data("username"));
$("#pname").val($(this).data("pname"));
$("#pnum").val($(this).data("pnum"));
$("#size").val($(this).data("size"));
});
4-2. 發送 PATCH 更新請求
當使用者在 Modal 中修改完「數量」與「尺寸」,點擊「確認更新」按鈕時,我們就要把新資料送給後端。請注意 HTTP Method 的使用方式:
- PUT: 替換整筆資料。如果只傳了數量,其他的名字、尺寸等欄位可能會被後端清空。
- PATCH: 部分更新。只會修改你有傳遞過去的欄位,其他欄位保持原樣。(現代開發推薦使用)
// 監聽 Modal 裡面的更新按鈕
$("#update_btn").click(function () {
// 1. 執行更新前的欄位驗證 (確保數量輸入正確)
if (flag_pnum) {
// 2. 取得使用者修改後的新數值
let pnum = $("#pnum").val();
let size = $("#size").val();
// 3. 發送 AJAX 請求至後端 API
$.ajax({
type: "PATCH", // 使用 PATCH 進行部分資料更新
url: `http://localhost:3000/product/${uid}`, // 網址必須加上該筆資料的 ID (${uid})
contentType: "application/json",
// 4. 將要更新的欄位打包成 JSON 字串送出
// (因為 username 跟 pname 被設定為 disabled 不允許修改,所以不送)
data: JSON.stringify({ pnum: pnum, size: size }),
success: function (data) {
// 更新成功後的處理
Swal.fire({
title: "更新成功!",
icon: "success",
confirmButtonText: "確認"
}).then((result) => {
if (result.isConfirmed) {
// 強制重新載入網頁,讓表格顯示最新資料
location.href = "20260310-CRUD-showTable.html";
}
});
},
error: function () {
Swal.fire({ title: "更新失敗", icon: "error" });
}
});
} else {
// 欄位驗證未通過的警告
Swal.fire({ title: "欄位有錯誤!", text: "數量必須為 1~10", icon: "error" });
}
});
核心觀念解析:
$(document).on(...)的必要性: 這是 jQuery 處理動態生成元素(Dynamically generated elements)的黃金法則。它利用了瀏覽器「事件冒泡(Event Bubbling)」的原理,把監聽器架設在最外層的document,來攔截內部所有按鈕的點擊。- 全域變數
uid: 在點擊「表格更新按鈕」到點擊「Modal 儲存按鈕」之間,程式經歷了兩次不同的事件。透過把 ID 存入全域變數uid,我們才能在最後一刻準確知道:剛剛使用者到底是在編輯哪一筆商品? - 精準打擊的 URL 路由:
url: `.../product/${uid}`,在 RESTful API 設計中,針對特定資料進行修改(PATCH/PUT)或刪除(DELETE)時,都必須在 URL 的結尾精準帶上該筆資料的 ID,後端才知道要操作資料庫裡的哪一列。
5. 刪除資料 (Delete)
刪除是資料操作中不可逆的行為。為了確保安全性與使用者體驗,我們必須實作「二次確認機制」,並正確地利用 RESTful API 的 DELETE 方法來通知伺服器移除資料。
5-1. 實作邏輯與流程拆解
刪除功能的運作分為:監聽點擊、使用者確認、發送請求、以及畫面同步四個步驟。
// 1. 監聽刪除按鈕 (同樣使用事件委派,確保能抓到動態生成的按鈕)
$(document).on("click", ".deleteBtn", function () {
// 2. 呼叫 SweetAlert2 彈出詢問視窗
Swal.fire({
title: "確認刪除?",
showDenyButton: true, // 顯示取消按鈕
icon: "question", // 顯示問號圖示
confirmButtonText: "確認",
denyButtonText: `取消`
}).then((result) => {
// 3. 使用者按下「確認」後才執行刪除邏輯
if (result.isConfirmed) {
// 抓取埋在按鈕上的資料 ID
let delete_id = $(this).data("id");
// 4. 發送 AJAX DELETE 請求
$.ajax({
type: "DELETE", // HTTP 方法:DELETE 用於移除資源
url: `http://localhost:3000/product/${delete_id}`, // 指定該筆 ID 的路徑
dataType: "json",
success: function (data) {
// 5. 刪除成功後,強制跳轉/重新載入頁面以同步資料
location.href = "20260310-CRUD-showTable.html";
},
error: function () {
Swal.fire({ title: "刪除失敗", icon: "error" });
}
});
}
});
});
核心觀念解析:
DELETE請求方法: 依照 RESTful 規範,刪除資料應使用DELETE方法。通常這類請求不需要傳送data本體(Payload),伺服器只需根據 URL 中的 ID 就能判斷要刪除哪一筆紀錄。- 使用者體驗 (UX) 與安全性: 直接執行刪除是非常危險的。透過 SweetAlert2 的
.then()判斷使用者是否真的點擊了isConfirmed,能有效防止因滑鼠誤觸而導致的資料流失。 - 資料同步策略: 在此範例中,成功刪除後使用了
location.href重新載入頁面。💡 優化思維:
雖然重整網頁最保險,但會造成畫面閃爍。在更進階的開發中,我們可以選擇「不重整網頁」,直接透過 jQuery 的$(this).closest('tr').remove()將該行表格從 DOM 中移除,達成更絲滑的視覺效果。
至此,我們已經完成了完整的資料生命週期管理。從 C (POST) 新增、R (GET) 讀取渲染、U (PATCH) 部分更新,到最後的 D (DELETE) 刪除,所有的動作都圍繞著「非同步 AJAX」與「資料屬性埋設」這兩大核心技術展開。